////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//         This file is distributed under the BSD 2-Clause open source license. See Licenses/License.txt for details.         //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNScripting/inc/WNScriptingEngineImpl.h"
#include "WNScripting/inc/WNBuiltinTypeInitialization.h"
#include "WNScripting/src/WNScriptASTLexer.hpp"
#include "WNScripting/src/WNScriptASTParser.hpp"
#include "WNScripting/inc/WNScopedVariableList.h"
#include "WNStrings/inc/WNStrings.h"
#include "WNScripting/inc/WNScriptingFactoryInternal.h"
#include "WNScripting/inc/WNScriptingMemoryManager.h"
#include "WNScripting/inc/WNCodeModule.h"
#include "WNScripting/inc/WNParameter.h"
#include "WNFileSystem/inc/WNFile.h"
#include "WNMemory/inc/WNAllocation.h"

#ifdef _WN_MSVC
    #pragma warning(push)
    #pragma warning(disable: 4100)
    #pragma warning(disable: 4127)
    #pragma warning(disable: 4152)
    #pragma warning(disable: 4244)
    #pragma warning(disable: 4245)
    #pragma warning(disable: 4267)
    #pragma warning(disable: 4355)
    #pragma warning(disable: 4512)
#endif

#include "llvm/IR/IRBuilder.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/ADT/APInt.h"

#ifdef _WN_MSVC
    #pragma warning(pop)
#endif

using namespace WNScripting;

static WNLogging::WNDefaultLogParameters<WNLogging::eError, 1024, true> mLogParams;

WNScriptingEngineImpl::WNScriptingEngineImpl(): mLogLevel(WNLogging::eWarning), mInternalLogger(&mConsoleLogger, mLogParams), mCompilationLog(&mInternalLogger) {
    mInternalLogger.SetLogLevel(mLogLevel);
}

WNScriptingEngineImpl::~WNScriptingEngineImpl() {
}

WN_VOID* WNScriptingEngineImpl::CompileLazyFile(WN_VOID* _scriptingEngine, WN_VOID* _requestedFunction) {
    WNScriptingEngine* scriptingEngine = reinterpret_cast<WNScriptingEngine*>(_scriptingEngine);
    WNFunctionDefinition* def = reinterpret_cast<WNFunctionDefinition*>(_requestedFunction);
    WN_VOID* funcPtr = WN_NULL;
    if( eWNOK != scriptingEngine->GetFunctionPointer(def->mContainedFile->mFileName, def->mName, def->mReturn, def->mTypes, funcPtr)) {
        return(WN_NULL);
    }
    return(funcPtr);
}

eWNTypeError WNScriptingEngineImpl::Initialize() {
    mInternalLogger.Log(WNLogging::eInfo, 0, "WNScriptingEngine Initialization Started");
    llvm::InitializeNativeTarget();
    llvm::InitializeNativeTargetAsmPrinter();
    llvm::InitializeNativeTargetAsmParser();
    mTypeManager = WNScriptingFactoryInternal::CreateTypeManager();
    eWNTypeError err = eWNOK;
    WNScriptType throwawayType;
    mTypeManager->RegisterScalarType("-Function", 0, throwawayType, 0);
    mTypeManager->RegisterScalarType("-Ptr", 0, throwawayType, llvm::Type::getInt32PtrTy(llvm::getGlobalContext()));
    mMemoryManager = WN_NEW WNScriptingMemoryManager(*this);

    if(( err = WNBuiltInInitializer::InitializeIntTypes(*mTypeManager)) != eWNOK) return err;
    if(( err = WNBuiltInInitializer::InitializeFloatTypes(*mTypeManager)) != eWNOK) return err;
    if(( err = WNBuiltInInitializer::InitializeScriptingCasts(*mTypeManager)) != eWNOK) return err;
    if(( err = WNBuiltInInitializer::InitializeFunctions(*this)) != eWNOK) return err;
    if(( err = RegisterCFunction("CompileLazyFunction", CompileLazyFile)) != eWNOK) return err;
    mInternalLogger.Log(WNLogging::eInfo, 0, "WNScriptingEngine Initialization Finished");
    return(eWNOK);
}

WNCodeModule* WNScriptingEngineImpl::GetCompiledModule(const WN_CHAR* _file) {
    WN_CHAR* c = WNStrings::WNStrNDup(_file, 1024);
    WNFileSystem::WNFile::CollapseFolderStructure(c);

    for(std::vector<std::pair<WN_CHAR*, WNCodeModule*> >::iterator i = mFileList.begin(); i != mFileList.end(); ++i) {
        if(WNStrings::WNStrCmp(c, i->first) == 0){
            WNMemory::WNFree(c);
            return(i->second);
        }
    }
    WNMemory::WNFree(c);
    return(WN_NULL);
}

eWNTypeError WNScriptingEngineImpl::DumpIntoModule(WNCodeModule*& _module, bool mCurrentFile, WNPreprocessedFile* _preprocessedFile, std::deque<WNPreprocessedFile*>& _nextToProcess) {
    if(_preprocessedFile->mMarked) {
        return(eWNOK);
    }

    for(std::vector<WNPreprocessedFile*>::iterator i = _preprocessedFile->mIncludedFiles.begin(); i != _preprocessedFile->mIncludedFiles.end(); ++i) {
        _nextToProcess.push_back(*i);
    }
    
    for(std::vector<WNFunctionDefinition*>::iterator i = _preprocessedFile->mFunctionDefinitions.begin(); i != _preprocessedFile->mFunctionDefinitions.end(); ++i) {
        if(mCurrentFile) {
            (*i)->mCurrentFile = true;
        } else {
            (*i)->mCurrentFile = false;
        }
        (*i)->mFunction = WN_NULL;
        _module->AddExternalScriptFunction(*i);
    }

    _preprocessedFile->mMarked = true;
    return(eWNOK);
}

eWNTypeError WNScriptingEngineImpl::DumpPreprocessedfile(WNCodeModule*& _module, WNPreprocessedFile* _preprocessedFile) {
    
    for(std::vector<WNPreprocessedFile*>::iterator i = mPreprocessedFiles.begin(); i != mPreprocessedFiles.end(); ++i) {
        (*i)->mMarked = false;
    }

    std::deque<WNPreprocessedFile*> toProcess;
    
    eWNTypeError err = eWNOK;
    
    if(eWNOK != (err = DumpIntoModule(_module, true, _preprocessedFile, toProcess))) {
        return(err);
    }
    for(std::vector<WNPreprocessedFile*>::iterator i = _preprocessedFile->mIncludedFiles.begin(); i != _preprocessedFile->mIncludedFiles.end(); ++i) {
        toProcess.push_back(*i);
    }
    while(!toProcess.empty()) {
        if(eWNOK != (err = DumpIntoModule(_module, false, *toProcess.begin(), toProcess))) {
            return(err);
        }
        toProcess.pop_front();
    }
    return(eWNOK);
}

eWNTypeError WNScriptingEngineImpl::PreprocessFile(const WN_CHAR* _file, WNCodeModule*& _module, WNPreprocessedFile*& _outFile) {
    char cBuff[2048] ;
    WNStrings::WNStrNCpy(cBuff, _file, 2047);
    WNFileSystem::WNFile::CollapseFolderStructure(cBuff);

    eWNTypeError err = eWNOK;
    
    for(std::vector<WNPreprocessedFile*>::iterator i = mPreprocessedFiles.begin(); i != mPreprocessedFiles.end(); ++i) {
        if(WNStrings::WNStrNCmp((*i)->mFileName, cBuff, 1024) == 0) {
            _outFile = *i;
            return(eWNOK);
        }
    }
    mInternalLogger.Log(WNLogging::eInfo, 0, "WNScriptingEngine Preprocessing File: ", _file);
    WNFileSystem::WNFile f;
    if(WNFileSystem::WNFile::eWNOK != f.OpenFile(cBuff, WNFileSystem::WNFile::eWNFMRead)) {
        return(eWNInvalidFile);
    }
    WN_CHAR* c = WNStrings::WNStrNDup(cBuff, 1024);

    const WN_CHAR* buff = f.GetDataBuffer();
    WNScriptASTLexer::InputStreamType input(reinterpret_cast<const ANTLR_UINT8*>(buff), ANTLR_ENC_8BIT, static_cast<ANTLR_UINT32>(f.GetFileSize()), reinterpret_cast<ANTLR_UINT8*>(c));

    WNScriptASTLexer lexer(&input);
    WNScriptASTParser::TokenStreamType tStream(ANTLR_SIZE_HINT, lexer.get_tokSource() );
    WNScriptASTParser parser(&tStream);

    WNScriptFile* scriptFile = parser.program();
    if(parser.getNumberOfSyntaxErrors() > 0 ||
        lexer.getNumberOfSyntaxErrors() > 0 ){
        WNMemory::WNFree(c);
        return(eWNError);
    }

    std::vector<WNScriptType> containedTypes;
    std::vector<WNFunctionDefinition*> containedFunctions;

    WNPreprocessedFile* preFile = WN_NEW WNPreprocessedFile();
    preFile->mFileName = c;

    if(eWNOK != (err = scriptFile->PreProcess(*_module, preFile->mExposedTypes, preFile->mFunctionDefinitions, *mCompilationLog))) {
        return(err);
    }
    mPreprocessedFiles.push_back(preFile);
    _outFile = preFile;
    WN_CHAR* folderName = WNFileSystem::WNFile::GetFolderName(cBuff);
    for(const WNScriptLinkedList<WN_CHAR>::WNScriptLinkedListNode* i = scriptFile->GetFirstInclude(); i != WN_NULL; i = i->next) {
        WN_CHAR filename[2048];
        WNStrings::WNStrNCpy(filename, folderName, WNStrings::WNStrLen(folderName) + 1);
        WNStrings::WNStrNCat(filename, "/", sizeof(filename) - WNStrings::WNStrLen(filename) - 1);
        WNStrings::WNStrNCat(filename, i->value+1, sizeof(filename) - WNStrings::WNStrLen(filename) - 1);
        filename[strlen(filename) - 1] = '\0';
        WNFileSystem::WNFile::CollapseFolderStructure(filename);
        WNPreprocessedFile* includedFile;
        if(eWNOK != (err = PreprocessFile(filename, _module, includedFile))) {
            return(err);
        }
        preFile->mIncludedFiles.push_back(includedFile);
    }
    
    if(eWNOK != (err = scriptFile->FinalizePreProcess(*_module, preFile->mFunctionDefinitions, *mCompilationLog))) {
        return(err);
    }

    for(std::vector<WNFunctionDefinition*>::iterator i = preFile->mFunctionDefinitions.begin(); i != preFile->mFunctionDefinitions.end(); ++i) {
        (*i)->mContainedFile = preFile;
    }

    return(eWNOK);    
}

eWNTypeError WNScriptingEngineImpl::CompileFile(const WN_CHAR* _file, WNCodeModule*& _module) {
    
    WNFileSystem::WNFile f;
    if(WNFileSystem::WNFile::eWNOK != f.OpenFile(_file, WNFileSystem::WNFile::eWNFMRead)) {
        return(eWNInvalidFile);
    }
    mInternalLogger.Log(WNLogging::eInfo, 0, "WNScriptingEngine Started Compiling File: ", _file);
    WN_CHAR* c = WNStrings::WNStrNDup(_file, 1024);
    WNFileSystem::WNFile::CollapseFolderStructure(c);

    const WN_CHAR* buff = f.GetDataBuffer();
    WNScriptASTLexer::InputStreamType input(reinterpret_cast<const ANTLR_UINT8*>(buff), ANTLR_ENC_8BIT, static_cast<ANTLR_UINT32>(f.GetFileSize()), reinterpret_cast<ANTLR_UINT8*>(c));

    WNScriptASTLexer lexer(&input);
    WNScriptASTParser::TokenStreamType tStream(ANTLR_SIZE_HINT, lexer.get_tokSource() );
    WNScriptASTParser parser(&tStream);

    WNScriptFile* scriptFile = parser.program();
    if(parser.getNumberOfSyntaxErrors() > 0 ||
        lexer.getNumberOfSyntaxErrors() > 0 ){
        WN_DELETE(scriptFile);
        WNMemory::WNFree(c);
        return(eWNError);
    }

    WNScopedVariableList* variableList = WNScriptingFactoryInternal::CreateScopedVariableList();
    _module = WN_NEW WNCodeModule(*mTypeManager, *variableList, this);
    if(eWNOK != _module->Initialize(eOptimized, *mMemoryManager)) {
        WN_DELETE(scriptFile);
        WNMemory::WNFree(c);
        WN_DELETE(_module);
        return(eWNError);
    }
    
    eWNTypeError err = eWNOK;
    WNPreprocessedFile* preFile;
    if(eWNOK != (err = PreprocessFile(_file,_module, preFile))) {
        return(err);
    }
    
    if(eWNOK != (err = DumpPreprocessedfile(_module, preFile))) {
        return(err);
    }

    for(std::vector<WNFunctionRegistry>::const_iterator i = mRegisteredFunctions.begin(); i != mRegisteredFunctions.end(); ++i) {
        if(eWNOK != (err = _module->AddExternalDefinition(i->mFunctionName, i->mRegisteredFunctionTag, i->mParams, i->mRetParam))) {
            WNMemory::WNFree(c);
            return(err);
        }
    }
    
    if(eWNOK != (err = scriptFile->DumpHeaders(*_module, *mCompilationLog))) {
        WNMemory::WNFree(c);    
        WN_DELETE(_module);
        return(err);
    }

    if(eWNOK != (err = scriptFile->GenerateCode(*_module, *mCompilationLog))) {
        WNMemory::WNFree(c);    
        WN_DELETE(_module);
        return(err);
    }

    WN_DELETE(variableList);
    WN_DELETE(scriptFile);
    mFileList.push_back(std::pair<WN_CHAR*, WNCodeModule*>(c, _module));
    
    for(WN_SIZE_T i = 0; i < preFile->mFunctionDefinitions.size(); ++i) {
        preFile->mFunctionDefinitions[i]->mFunctionPointer = _module->GetExecutionEngine()->getPointerToFunction(preFile->mFunctionDefinitions[i]->mFunction);
    }
    mInternalLogger.Log(WNLogging::eInfo, 0, "WNScriptingEngine Finished Compiling File: ", _file);
    return(eWNOK);
}

eWNTypeError WNScriptingEngineImpl::GetFunctionPointer(const WN_CHAR* _file, const WN_CHAR* _functionName, const WNScriptType _retParam, const std::vector<WNScriptType>& _params, void*& _ptr) {
    WNCodeModule* codeModule = GetCompiledModule(_file);
    if(!codeModule) {
        eWNTypeError err = CompileFile(_file, codeModule);
        if(err != eWNOK) {
            printf("Could not compile file %s\n", _file);
            return(err);
        }
    }
    WNFunctionDefinition* f = codeModule->GetFunctionDefinition(_functionName, _params);
    
    if(!f || !(f->mReturn == _retParam)) {
        mInternalLogger.Log(WNLogging::eWarning, 0, "Function ", _functionName, " does not exist in file", _file);
        return(eWNDoesNotExist);
    }
    
    _ptr = f->mFunctionPointer;
    return(eWNOK);
}

WNScriptType WNScriptingEngineImpl::GetTypeByName(const WN_CHAR* _typeName) {
    if(!mTypeManager) {
        return(0);
    }
    WNScriptType t;
    if(eWNOK != mTypeManager->GetTypeByName(_typeName, t)) {
        return(0);
    }
    return(t);
}

eWNTypeError WNScriptingEngineImpl::RegisterFunction(const WN_CHAR* _functionName, const WNScriptType _retParam, const std::vector<WNScriptType>& _params, void* _ptr) {
    if(!mTypeManager) {
        mInternalLogger.Log(WNLogging::eCritical, 0, "Type manager was not correctly created ", _functionName);
        return(eWNError);
    }

    for(std::vector<WNFunctionRegistry>::const_iterator i = mRegisteredFunctions.begin(); i != mRegisteredFunctions.end(); ++i) {
        if(WNStrings::WNStrNCmp(_functionName, i->mFunctionName, 256) == 0) {
            if(i->mParams.size() != _params.size()) {
                continue;
            }
            std::vector<WNScriptType>::const_iterator newIterator = _params.begin();
            std::vector<WNScriptType>::const_iterator oldIterator = i->mParams.begin();
            for(;newIterator != _params.end() && oldIterator != i->mParams.end(); ++newIterator, ++oldIterator) {
                if(*newIterator != *oldIterator) {
                    break;
                }
                if(!mTypeManager->GetCastingOperation(*newIterator, *oldIterator)) {
                    if(!mTypeManager->GetCastingOperation(*oldIterator, *newIterator)) {
                        break;   
                    }
                }
            }
            if(newIterator == _params.end() || oldIterator == i->mParams.end()) {
                mInternalLogger.Log(WNLogging::eWarning, 0, "Function already exists ", _functionName);
                return(eWNAlreadyExists);
            }
        }
    }
    mInternalLogger.Log(WNLogging::eInfo, 0, "WNScriptingEngine Function Registered: ", _functionName);
    WN_SIZE_T len = WNStrings::WNStrLen(_functionName);
    WN_CHAR* functionName = WNMemory::WNMallocT<WN_CHAR>(len + 4 * _params.size() + 2);
    functionName[len + 4*_params.size() + 1] = '\0';
    WNMemory::WNMemCpy(functionName, _functionName, len);
    WN_CHAR* loc = functionName + len + 1;
    functionName[len] = '@';
    for(std::vector<WNScriptType>::const_iterator i = _params.begin(); i != _params.end(); ++i) {
        WNMemory::WNMemCpy(loc, (*i)->mTag, 4);
        loc += 4;
    }

    mRegisteredFunctions.push_back(WNFunctionRegistry());
    mRegisteredFunctions.back().functionPointer = _ptr;
    mRegisteredFunctions.back().mFunctionName = WNStrings::WNStrNDup(_functionName, 256);                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               ;
    mRegisteredFunctions.back().mRegisteredFunctionTag = functionName;
    mRegisteredFunctions.back().mParams.assign(_params.begin(), _params.end());
    mRegisteredFunctions.back().mRetParam = _retParam;
    return(eWNOK);
}

void* WNScriptingEngineImpl::GetRegisteredFunction(const WN_CHAR* _functionName) const {
    for(std::vector<WNFunctionRegistry>::const_iterator i = mRegisteredFunctions.begin(); i != mRegisteredFunctions.end(); ++i) {
        if(WNStrings::WNStrNCmp(_functionName, i->mRegisteredFunctionTag, 1024) == 0) {
            return(i->functionPointer);
        }
    }
    return(WN_NULL);
}


WN_VOID WNScriptingEngineImpl::SetInternalLogLevel(WNLogging::WNLogLevel _level) {
    mInternalLogger.SetLogLevel(_level);
    mLogLevel = _level;
}

WN_VOID WNScriptingEngineImpl::SetLogLevel(WNLogging::WNLogLevel _level) {
    mCompilationLog->SetLogLevel(_level);
}

WN_VOID WNScriptingEngineImpl::SetCompilationLog(WNLogging::WNLog* _compilationLog) {
    if(!_compilationLog) 
    {
        mCompilationLog = &mInternalLogger;
    }
    else
    {
        mCompilationLog = _compilationLog;
    }
}

WN_VOID WNScriptingEngineImpl::BufferFlushCallback(WN_VOID* _context, const WN_CHAR* buffer, WN_SIZE_T bufferLength, const std::vector<WNLogging::WNLogColorElement>& colors) {
    WNScriptingEngineImpl* impl = reinterpret_cast<WNScriptingEngineImpl*>(_context);
    if(!impl->mErrorCallback.IsValid()) {
        return;
    }
    impl->mErrorCallback.Execute(buffer, bufferLength, colors);   
}
